package objects;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.Timer;
import start.Config;

/**
 * Steuerung und Animation der Uhrzeiger.
 * 
 * Hinweis: 1 Minute auf dem Ziffernblatt entsprechen 
 * einer Zeigerbewegung von 6 Grad.
 *
 * @version 1.0
 * @author Imanuel Rohlfing
 * @author Dennis Wittkoetter
 */
public class Hands implements ActionListener {
    /**
     * Stundenzeiger
     */
    private TransformGroup hoursTG;
    /**
     * Minutenzeiger
     */
    private TransformGroup minutesTG;
    /**
     * aktuelle Position des Minutenzeigers
     */
    private int minutePos;
    /**
     * aktuelle Position des Stundenzeigers
     */
    private int hourPos;
    /**
     * aktuelle Sekunde
     */
    private int second;
    /**
     * Drehrichtung der Zeiger
     */
    private int direction;
    /**
     * Zeigergeschwindigkeit
     */
    private Timer timer;
    /**
     * Rotationswinkel der Zeiger
     */
    private int rotDegree;
    
    /**
     * Konstruiert Zeiger-Set bestehend aus einem Stunden- und Minutenzeiger.
     *
     * @param hoursTG	Stundenzeiger
     * @param minutesTG	Minutenzeiger
     * 
     * @see Config#defaultHandsDegree
     * @see Config#defaultHandsSpeed
     */
    public Hands(TransformGroup hoursTG, TransformGroup minutesTG) {
        this.hoursTG = hoursTG;
        this.minutesTG = minutesTG;
        this.hourPos = 0;
        this.minutePos = 0;
        this.second = 0;
        this.direction = 1;
        this.rotDegree = Config.defaultHandsDegree;

        //Berechtigungen setzen/
        hoursTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        minutesTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        //Timer setzen (feuert jede Sekunde)
        timer = new Timer(Config.defaultHandsSpeed, this);
    }

    /**
     * Startet der Zeigeranimationen
     */
    public void start() {
        if (!timer.isRunning()) {
            timer.start();
        }
    }

    /**
     * Stoppt die Zeigeranimationen
     */
    public void stop() {
        if (timer.isRunning()) {
            timer.stop();
        }
    }
    
    /**
     * Versetzt den Minutenzeiger um die angegeben Anzahl an 
     * Grad in die festgelegte Richtung.
     * 
     * @param degree	Grad um den der Zeiger versetzt wird
     * @return			Minutenzeigerposition 
     * 
     * @see Hands#direction
     * @see Hands#setMovementBackward()
     * @see Hands#setMovementForward()
     */
    public int moveMinuteHand(int degree) {
        Transform3D rotate = new Transform3D();
        
        //Negative Winkelwerte sind nicht erlaubt.
        //Die Drehrichtung wird durch die direction-Variable festgelegt.
        if (degree < 0)	degree *= -1;
        
        //neuer Winkel fuer Zeigerausrichtung
        minutePos += (direction * degree);
        
        //Zeiger drehen
        rotate.rotX((Math.PI / 180) * minutePos);
        minutesTG.setTransform(rotate);

        //Positionswert nach einer vollen Umdrehung zuruecksetzen
        if (minutePos == (360*direction))
            minutePos = 0;
       
        return minutePos;
    }
    
    /**
     * Versetzt den Stundenzeiger um die angegeben Anzahl an 
     * Grad festgelegte Richtung.
     * 
     * @param degree	Grad um den der Zeiger versetzt wird
     * @return			Minutenzeigerposition
     * 
     * @see Hands#direction
     * @see Hands#setMovementBackward()
     * @see Hands#setMovementForward()
     */
    public int moveHourHand(int degree) {
        Transform3D rotate = new Transform3D();
        
        //Negative Winkelwerte sind nicht erlaubt.
        //Die Drehrichtung wird durch die direction-Variable festgelegt.
        if (degree < 0)	degree *= -1;
        
        //neuer Winkel fuer Zeigerausrichtung
        hourPos += (direction * degree);
        
        //Zeiger drehen
        rotate.rotX((Math.PI / 180) * hourPos);
        hoursTG.setTransform(rotate);
        
        //Positionswert nach einer vollen Umdrehung zuruecksetzen
        if (hourPos == (360*direction))
            hourPos = 0;
       
        return minutePos;
    }
    
    /**
     * Versetzt den Minutenzeiger um die angegeben Anzahl an 
     * Grad in die aktuell festgelegte Richtung und zieht 
     * dazu im Verhaeltnis den Stundenzeiger mit.
     * 
     * WICHTIG: Die Winkel-Werte koennen beliebig sein, solange
     * sie kleiner gleich 12 sind. Werte groesser 12 muessen ein
     * Vielfaches von 12 sein, damit der Stundenzeiger im korrekten
     * Verhaeltnis mitgezogen werden kann.
     * 
     * @param degree	Grad um den der Zeiger versetzt wird
     * @return			Minutenzeigerposition
     * 
     * @see Hands#direction
     * @see Hands#setMovementBackward()
     * @see Hands#setMovementForward()
     * 
     */
    public int moveBothHands(int degree) {    	
        Transform3D rotate = new Transform3D();
        
        //Negative Winkelwerte sind nicht erlaubt.
        //Die Drehrichtung wird durch die direction-Variable festgelegt.
        if (degree < 0)	degree *= -1;
        
        //Winkelwerte, die kein Vielfaches von 12 sind, abrunden 
        if (degree > 12) degree = degree - (degree % 12);
        
        //WICHTIG! Stehen lassen!
        //Stellt sicher das sich der Stundenzeiger nicht irgendwann
        //im falschen Verhaeltnis zum Minutenzeiger bewegt.
        int hourBalance = degree / 12;
        if (minutePos % degree != 0)
        	degree = 1;
        
        //neuer Winkel fuer Zeigerausrichtung
        minutePos += (direction * degree);
        
        //Zeiger drehen
        rotate.rotX((Math.PI / 180) * minutePos);
        minutesTG.setTransform(rotate);

        //Stundenzeiger mitziehen
        if (minutePos % 12 == 0) {
        	if (degree <= 12)
        		moveHourHand(1);
        	else
        		moveHourHand(hourBalance);
        }
        
        //Positionswert nach einer vollen Umdrehung zuruecksetzen
        if (minutePos == (360*direction))
            minutePos = 0;
       
        return minutePos;
    }   
    
    /**
     * Setzt die Bewegungsrichtung der Zeiger auf vorwaertsdrehend.
     */
    public void setMovementForward() {
    	direction = 1;
    }
    
    /**
     * Setzt die Bewegungsrichtung der Zeiger auf rueckwaertsdrehend.
     */
    public void setMovementBackward() {
    	direction = -1;
    }
    
    /**
     * Liefert die Drehrichtung der Uhrzeiger.
     * @return	-1 = gegen den Uhrzeigersinn; 1 = im Uhrzeigersinn
     */
    public int getDirection() {
    	return direction;
    }
    
    /**
     * Liefert die aktuelle, von der Uhr angezeigte, Minute.
     *
     * @return
     */
    public int getMinute() {
        int minute = minutePos / 6;
        if (minute < 0) {
            return 60 + minute;
        }

        return minute;
    }

    /**
     * Liefert die aktuelle Sekunde.
     * @return
     */
    public int getSecond() {
        return second;
    }

    /**
     * Liefert die aktuelle, von der Uhr angezeigte, Stunde.
     * @return
     */
    public int getHour() {
        int hour = hourPos / 30;
        if (hour < 0) {
            return 24 + hour;
        }

        return hour;
    }

    /**
     * Aendert die Geschwindigkeit der Timer-Aufrufe auf den angegebenen Wert.
     * @param newDelay	Wert in Millisekunden
     */
    public void setTimerDelay(int newDelay) {
    	this.stop();
    	timer = new Timer(newDelay, this);
    	this.start();
    }
    
    /**
     * Liefert Timer-Geschwindigkeit.
     * @return	Wert in Millisekunden
     */
    public int getTimerDelay() {
    	return timer.getDelay();
    }
    
    /**
     * Setzt den Winkel in dem der Minutenzeiger in Intervall versetzt wird.
     * @param d	Winkelwert in Grad
     */
    public void setRotDegree(int d) {
    	this.rotDegree = d;
    }
    
    /**
     * Liefert den Winkel in dem der Minutenzeiger in Imtervall versetzt wird.
     * @return	Winkelwert in Grad
     */
    public int getRotDegree() {
    	return rotDegree;
    }
        
    /**
     * Timer-Aufruf
     * Zaehlt Sekunden und setzt Zeiger vor.
     */
    public void actionPerformed(ActionEvent e) {
        second++;	//Sekunde hochzaehlen

        if (second == 60) {
            second = 0;		//Sekunde zuruecksetzen
        }
        if (second % 10 == 0) {
            moveBothHands(getRotDegree());	//alle 10 Sekunden die Zeiger 1 Grad vor setzen
        }
    }    
}
